<?php
/**
 * Allows to save and read objects data from/into database.
 * Object is represented by table.
 * We assume that object id has auto increment property if it is integer
 * and it is not specified when object is created.
 * 
 * @author Damian Sromek <damianpaq@gmail.com>
 * @version 0.1
 */
// TODO: store all currently read models?
// TODO: save all models in__destructor?
// TODO: comment
abstract class Plus_Model_Mapper_DbTable_Abstract extends Plus_Model_Mapper_Abstract
{
	/** 
	 * Table in wich object is stored.
	 * @var Zend_Db_Table_Abstract 
	 */
	protected $_dbTable;
	
	/**
	 * Name of the class allowed as the DbTable for this mapper.
	 * @var Zend_Db_Table_Abstract child
	 */
	//protected $_dbTableClassName;
	
	/**
	 * Name of the class wich represents model of saved object.
	 * @var Plus_Model_Model_Abstract
	 */
	protected $_supportedModelClassName;
	
	/**
	 * Constructor.
	 * Sets mapper database table that will be used.
	 * $options requires array with following keys and values: (
	 * 	'dbTable' => Zend_Db_Table,
	 * 	'supportedModelClassName' => 'name of supported model class'
	 * )
	 * 
	 * @param $options array with contructor options
	 * @return void
	 * @throws Exception if $supportedModelClassName class doesn't exist
	 */
	// TODO: update comment
	protected function __construct($options)
	{
		if (!is_array($options) || empty($options)) {
			throw new Exception("Constructor requires array of options with keys:
				'dbTable' and 'supportedModelClassName'");
		}
		
		parent::__construct();
		$this->setDbTable($options['dbTable']);
		if (!class_exists($options['supportedModelClassName'])) {
			throw new Exception('Class does not exists. ' . $options['supportedModelClassName']);
		}
		$this->setSupportedModelClassName($options['supportedModelClassName']);
		$this->_log->info(get_class($this) . ' - OK');
	}
	
	/**
	 * Sets database table used as storage of object's data.
	 * @param $dbTable database table we want to use as storage 
	 * @return $this
	 * @throws Exception if $dbTable isn't instance of Zend_Db_Table_Abstract
	 */
	// TODO: update comment
	protected function setDbTable($dbTable)
	{
		//$this->_log->info(get_class($this) . ' - checking table...');
		// FIXME: allow to work with child classes?
		/*if (get_class($dbTable) != $this->getAllowedDbTableClass()) {
			$this->_log->err(get_class($this) . ' - Invalid table data gateway provided');
			throw new Exception('Invalid table data gateway provided: ' . get_class($dbTable));
		}*/
		if (null == $dbTable) {
			throw new Exception('Wrong table specified');
		}
		$this->_dbTable = $dbTable;
		//$this->_log->info(get_class($dbTable) . ' is good table object for: ' . get_class($this));
		return $this;
	}
	
	/**
	 * Sets model class name that are supported by this mapper.
	 * @param $supportedModelClassName model class name
	 * @return $this
	 */
	protected function setSupportedModelClassName($supportedModelClassName)
	{
		$this->_supportedModelClassName = $supportedModelClassName;
		return $this;
	}
	
	/**
	 * 
	 * @return database table used for data storage
	 */
	protected function getDbTable()
	{
		return $this->_dbTable;
	}
	
	/**
	 * Returns name of allowed DbTable class for this mapper.
	 * @return Zend_Db_Table_Abstract or its child
	 */
	/*protected function getAllowedDbTableClass()
	{
		return ($this->_dbTableClassName !== null) ? $this->_dbTableClassName 
			: 'Zend_Db_Table_Abstract';
	}*/
	
	/**
	 * Saves specified object's data to database table.
	 * (non-PHPdoc)
	 * @see application/models/Mapper/Plus_Model_Mapper_Abstract#save()
	 */
	public function save($object)
	{
		$this->_log->info(get_class($this) . ' - checking model...');
		// FIXME: check if passed object is supported by this model
		if (!$object instanceof $this->_supportedModelClassName) {
			$this->_log->err(get_class($this) . ' - Inproper model type.');
			throw new Exception('Inproper model type. Should be ' 
				. $this->_supportedModelClassName);
		}
		$this->_log->info(get_class($this) . ' - model ok.');
		$this->_log->info(get_class($this) . ' - getting object\'s data');
		$data = $object->getTableDataArray();
		// FIXME: probably not much optimal performace
		$update = false;
		if ($object->getId() != '') {
			$existing = $this->find($object->getId());
			if ($existing !== null) {
				$update = true;
			}
		}
		
		if (!$update) {
			//unset($data[$object->getIdName()]);
			$this->getDbTable()->insert($data);	
			$this->_log->info(get_class($this) . ' object saved as new');
		} else {
			$this->getDbTable()->update($data, 
				array(
					$object->getIdColumn() . ' = ?' => $object->getId())
				);
			$this->_log->info(get_class($this) . ' object updated - ' . $object->getId());
		}
	}
	
	/**
	 * Finds in database row/data with specified id and sets them as specified object's data.
	 * @param $id id of searched row
	 * @param $object object that found data should be put into
	 * @return found model or null if model with specified id doesn't exist
	 */
	public function find($id)
	{
		// FIXME: check if passed object is supported by this model
		$this->_log->info(get_class($this) . ' looking for object with id = ' . $id);
		$result = $this->getDbTable()->find($id);
		if (0 == count($result)) {
			$this->_log->info(get_class($this) . ' object with that id not found - ' . $id);
			return null;
		}
		$row = $result->current();
		//var_dump($row);
		$this->_log->info(get_class($this) . ' object with that id found - ' . $id);
		$found = new $this->_supportedModelClassName();
		$found->setOptionsFromRow($row);
		//echo $foundCountry->getName();
		return $found;
	}
	
	public function get($id)
	{
		$found = $this->find($id);
		
		if ($found === null) {
			throw new Exception('Object with id "' . $id . '" does not exists');
		}
		
		return $found;
	}
	
	/**
	 * Returns all objects build from the data in the database.
	 * @return array all objects build from database
	 */
	// TODO: read only partial data?
	public function fetchAll()
	{
		$this->_log->info(get_class($this) . ' fetching all objects');
		$resultSet = $this->getDbTable()->fetchAll();
		//var_dump($resultSet);
		$entries = array();
		foreach ($resultSet as $row) {
			$this->_log->info(get_class($this) . ' - creating read object');
			//var_dump($row);
			$entry = new $this->_supportedModelClassName();
			$entry->setOptionsFromRow($row);
			$entries[] = $entry;
		}
		$this->_log->info(get_class($this) . ' fetched objects number - ' . count($entries));
		return $entries;
	}
	
	public function delete($object)
	{
		if ($object->getId() == null) {
			throw new Exception('Can not delete not initialized object.');
		}
		$table = $this->getDbTable();
		$where = $table->getAdapter()->quoteInto(
			$object->getIdColumn() . ' = ?', $object->getId());
		//var_dump($object->getId());
		$table->delete($where);
	}
}